﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace LpbPrj.Client.Tools.Helper
{
    public enum JoinState : short
    {

        //[Remark("内连接（Source、Inner交集）")]
        Join = 0,

        //[Remark("左连接（Source全部包含）")]
        LeftJoin = 1,

        //[Remark("右连接（Inner全部包含）")]
        RightJoin = 2,

        //[Remark("全连接（Source、Inner合集）")]
        FullOuterJoinJoin = 3,

        //[Remark("左排除连接（Source去掉交集部分）")]
        LeftExcludingJoin = 4,

        //[Remark("右排除连接（Inner去掉交集部分）")]
        RightExcludingJoin = 5,

        //[Remark("全排除连接（合集去掉交集部分）")]
        FulltExcludingJoin = 6
    }


    public static class JoinExtensions
    {
        public static IEnumerable<TResult> JoinExtension<TSource, TInner, TKey, TResult>(this IEnumerable<TSource> source,          //Source Collection
                                                                                         IEnumerable<TInner> inner,                 //Inner Collection
                                                                                         Func<TSource, TKey> pk,                    //PK
                                                                                         Func<TInner, TKey> fk,                     //FK
                                                                                         JoinState state,                           //JoinState
                                                                                         Func<TSource, TInner, TResult> result)     //Result Collection
        {
            IEnumerable<TResult> _result = Enumerable.Empty<TResult>();

            switch (state)
            {
                case JoinState.Join:
                    {
                        _result = source.Join(inner, pk, fk, result);
                    }
                    break;
                case JoinState.LeftJoin:
                    {
                        _result = source.LeftJoin(inner, pk, fk, result);
                    }
                    break;
                case JoinState.RightJoin:
                    {
                        _result = source.RightJoin(inner, pk, fk, result);
                    }
                    break;
                case JoinState.FullOuterJoinJoin:
                    {
                        _result = source.FullOuterJoinJoin(inner, pk, fk, result);
                    }
                    break;
                case JoinState.LeftExcludingJoin:
                    {
                        _result = source.LeftExcludingJoin(inner, pk, fk, result);
                    }
                    break;
                case JoinState.RightExcludingJoin:
                    {
                        _result = source.RightExcludingJoin(inner, pk, fk, result);
                    }
                    break;
                case JoinState.FulltExcludingJoin:
                    {
                        _result = source.FulltExcludingJoin(inner, pk, fk, result);
                    }
                    break;
                default:
                    break;
            }

            return _result;
        }


        /// <summary>
        /// 左连接（Source全部包含）
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TInner"></typeparam>
        /// <typeparam name="TKey"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="source"></param>
        /// <param name="inner"></param>
        /// <param name="pk"></param>
        /// <param name="fk"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        static IEnumerable<TResult> LeftJoin<TSource, TInner, TKey, TResult>(this IEnumerable<TSource> source, IEnumerable<TInner> inner, Func<TSource, TKey> pk, Func<TInner, TKey> fk, Func<TSource, TInner, TResult> result)
        {
            IEnumerable<TResult> _result = Enumerable.Empty<TResult>();

            _result = from s in source
                      join i in inner
                      on pk(s) equals fk(i) into joinData
                      from left in joinData.DefaultIfEmpty()
                      select result(s, left);

            return _result;
        }

        /// <summary>
        /// 右连接（Inner全部包含）
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TInner"></typeparam>
        /// <typeparam name="TKey"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="source"></param>
        /// <param name="inner"></param>
        /// <param name="pk"></param>
        /// <param name="fk"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        static IEnumerable<TResult> RightJoin<TSource, TInner, TKey, TResult>(this IEnumerable<TSource> source, IEnumerable<TInner> inner, Func<TSource, TKey> pk, Func<TInner, TKey> fk, Func<TSource, TInner, TResult> result)
        {
            IEnumerable<TResult> _result = Enumerable.Empty<TResult>();

            _result = from i in inner
                      join s in source
                      on fk(i) equals pk(s) into joinData
                      from right in joinData.DefaultIfEmpty()
                      select result(right, i);

            return _result;
        }

        /// <summary>
        /// 全连接（Source、Inner合集）
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TInner"></typeparam>
        /// <typeparam name="TKey"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="source"></param>
        /// <param name="inner"></param>
        /// <param name="pk"></param>
        /// <param name="fk"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        static IEnumerable<TResult> FullOuterJoinJoin<TSource, TInner, TKey, TResult>(this IEnumerable<TSource> source, IEnumerable<TInner> inner, Func<TSource, TKey> pk, Func<TInner, TKey> fk, Func<TSource, TInner, TResult> result)
        {

            var left = source.LeftJoin(inner, pk, fk, result).ToList();
            var right = source.RightJoin(inner, pk, fk, result).ToList();

            return left.Union(right);


        }

        /// <summary>
        /// 左排除连接（Source去掉交集部分）
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TInner"></typeparam>
        /// <typeparam name="TKey"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="source"></param>
        /// <param name="inner"></param>
        /// <param name="pk"></param>
        /// <param name="fk"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        static IEnumerable<TResult> LeftExcludingJoin<TSource, TInner, TKey, TResult>(this IEnumerable<TSource> source, IEnumerable<TInner> inner, Func<TSource, TKey> pk, Func<TInner, TKey> fk, Func<TSource, TInner, TResult> result)
        {
            IEnumerable<TResult> _result = Enumerable.Empty<TResult>();

            _result = from s in source
                      join i in inner
                      on pk(s) equals fk(i) into joinData
                      from left in joinData.DefaultIfEmpty()
                      where left == null
                      select result(s, left);

            return _result;
        }

        /// <summary>
        /// 右排除连接（Inner去掉交集部分）
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TInner"></typeparam>
        /// <typeparam name="TKey"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="source"></param>
        /// <param name="inner"></param>
        /// <param name="pk"></param>
        /// <param name="fk"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        static IEnumerable<TResult> RightExcludingJoin<TSource, TInner, TKey, TResult>(this IEnumerable<TSource> source, IEnumerable<TInner> inner, Func<TSource, TKey> pk, Func<TInner, TKey> fk, Func<TSource, TInner, TResult> result)
        {
            IEnumerable<TResult> _result = Enumerable.Empty<TResult>();

            _result = from i in inner
                      join s in source
                      on fk(i) equals pk(s) into joinData
                      from right in joinData.DefaultIfEmpty()
                      where right == null
                      select result(right, i);

            return _result;
        }

        /// <summary>
        /// 全排除连接（合集去掉交集部分）
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TInner"></typeparam>
        /// <typeparam name="TKey"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="source"></param>
        /// <param name="inner"></param>
        /// <param name="pk"></param>
        /// <param name="fk"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        static IEnumerable<TResult> FulltExcludingJoin<TSource, TInner, TKey, TResult>(this IEnumerable<TSource> source, IEnumerable<TInner> inner, Func<TSource, TKey> pk, Func<TInner, TKey> fk, Func<TSource, TInner, TResult> result)
        {
            var left = source.LeftExcludingJoin(inner, pk, fk, result).ToList();
            var right = source.RightExcludingJoin(inner, pk, fk, result).ToList();

            return left.Union(right);
        }

    }
}
